﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input.Touch;
using BlawkayMobileLibrary;

namespace Bounce
{
    public abstract class GameType : Screen
    {
        public GameType(GamePage oPage)
            : base()
        {
            GamePage = oPage;
        }

        public override void Initialize(ContentManager oContentManager, GraphicsDevice oDevice)
        {
            BasketBalls = new List<Basketball>();
            Goals = new List<Goal>();
            SpriteBatch = new SpriteBatch(oDevice);

            Resolution = new Vector2(oDevice.Viewport.Width, oDevice.Viewport.Height);
            Wall = new Rectangle(0, 0, (int)Resolution.X, (int)Resolution.Y);
            BounceFriction = 0.2f;
            RollFriction = 0.03f;
            AirFriction = 0.0f;
            GravityStrength = 0.52f;
            GravityDirection = new Vector2(0, 1);
        }

        public override sealed void Update(float fElapsedTime)
        {
            Input();
            Move(fElapsedTime);
            CheckCollisions();
        }
        public override sealed void Draw(float fElapsedTime)
        {
            SpriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);

            foreach (Basketball oBall in BasketBalls)
            {
                oBall.Draw(SpriteBatch);
            }

            foreach (Goal oGoal in Goals)
            {
                oGoal.Draw(SpriteBatch);
            }

            DrawGameType();

            SpriteBatch.End();
        }

        protected abstract void Input();
        protected abstract void CheckCollisions();
        protected abstract void DrawGameType();

        void Move(float fElapsedTime)
        {
            foreach (Basketball oBall in BasketBalls)
            {
                oBall.PreviousPosition = oBall.Position;
                oBall.PreviousVelocity = oBall.Velocity;

                oBall.Velocity += (100 * GravityStrength) * GravityDirection * fElapsedTime;
                oBall.Velocity *= (1 - AirFriction);
                oBall.FrameVelocity = oBall.Velocity;
                oBall.Move();
            }
        }

        #region Collision Methods

        protected void CheckCollisions_Walls()
        {
            foreach (Basketball oBall in BasketBalls)
            {
                Vector2 vVelocity = oBall.Velocity;
                Vector2 vPosition = oBall.Position;

                if (oBall.Position.X + oBall.Radius > Wall.Right)
                {
                    vVelocity.X = -System.Math.Abs(oBall.Velocity.X);
                    vPosition.X = Wall.Right - oBall.Radius;
                }
                else if (oBall.Position.X - oBall.Radius < Wall.Left)
                {
                    vVelocity.X = System.Math.Abs(oBall.Velocity.X);
                    vPosition.X = Wall.Left + oBall.Radius;
                }

                if (oBall.Position.Y + oBall.Radius > Wall.Bottom)
                {
                    vVelocity.Y = -System.Math.Abs(oBall.Velocity.Y);
                    vPosition.Y = Wall.Bottom - oBall.Radius;
                    vVelocity.Y *= (1 - BounceFriction);
                    vVelocity.X *= (1 - RollFriction);
                }
                else if (oBall.Position.Y - oBall.Radius < Wall.Top)
                {
                    vVelocity.Y = System.Math.Abs(oBall.Velocity.Y);
                    vPosition.Y = Wall.Top + oBall.Radius;
                }

                oBall.Velocity = vVelocity;
                oBall.Position = vPosition;
            }
        }
        protected void CheckCollisions_Balls()
        {
            // Basketball-Basketball Collision
            for (int i = 0; i < BasketBalls.Count - 1; i++)
            {
                for (int j = i + 1; j < BasketBalls.Count; j++)
                {
                    Basketball oBall_A = BasketBalls[i];
                    Basketball oBall_B = BasketBalls[j];

                    if (oBall_A == oBall_B)
                        continue;

                    if (oBall_A.Position == oBall_B.Position)
                        oBall_B.Position += Vector2.UnitX;

                    float fStep = 5.0f;
                    Vector2 vPostiionA_Step = oBall_A.PreviousPosition;
                    Vector2 vPostiionB_Step = oBall_B.PreviousPosition;
                    Vector2 vVelocityA_Step = oBall_A.Velocity / fStep;
                    Vector2 vVelocityB_Step = oBall_B.Velocity / fStep;
                    for (int t = 0; t < fStep; t++)
                    {
                        vPostiionA_Step += vVelocityA_Step;
                        vPostiionB_Step += vVelocityB_Step;

                        if (Circle.HasCollisionBetween(vPostiionA_Step, oBall_A.Radius, vPostiionB_Step, oBall_B.Radius))
                        {
                            oBall_A.Position = vPostiionA_Step;
                            oBall_B.Position = vPostiionB_Step;

                            Bounce_Ball_Ball(oBall_A, oBall_B);
                            Penetration_Ball(oBall_A, oBall_B);
                            break;
                        }
                    }

                }
            }
        }
        protected void CheckCollisions_Goals()
        {
            // Basketball-Goal Collision
            for (int i = 0; i < BasketBalls.Count; i++)
            {
                for (int j = 0; j < Goals.Count; j++)
                {
                    Basketball oBall = BasketBalls[i];
                    Goal oGoal = Goals[j];

                    float fStep = 10;
                    Vector2 vPostiion_Step = oBall.PreviousPosition;
                    Vector2 vVelocity_Step = oBall.Velocity / fStep;
                    for (int t = 0; t < fStep; t++)
                    {
                        vPostiion_Step += vVelocity_Step;

                        if (Circle.HasCollisionBetween(vPostiion_Step, oBall.Radius, oGoal.Position_Rim_Left, Goal.RIM_RADIUS))
                        {
                            oBall.Position = vPostiion_Step;
                            Bounce_Ball_Rim(oBall, oGoal.Position_Rim_Left);
                            Penetration_Circle(oBall, oGoal.Position_Rim_Left);
                            break;
                        }
                        if (Circle.HasCollisionBetween(vPostiion_Step, oBall.Radius, oGoal.Position_Rim_Right, Goal.RIM_RADIUS))
                        {
                            oBall.Position = vPostiion_Step;
                            Bounce_Ball_Rim(oBall, oGoal.Position_Rim_Right);
                            Penetration_Circle(oBall, oGoal.Position_Rim_Right);
                            break;
                        }
                    }

                }
            }
        }

        void Bounce_Ball_Ball(Basketball Ball_A, Basketball Ball_B)
        {
            /////////////////////////////////////////////////////////////////
            //// http://freespace.virgin.net/hugo.elias/models/m_snokr.htm //
            /////////////////////////////////////////////////////////////////

            Vector2 Impact = Ball_A.Position - Ball_B.Position;
            Vector2 Impulse = Vector2.Normalize(Ball_A.Position - Ball_B.Position);
            //float ImpactSpeed = Vector2.Dot(Impulse, Impact);
            float ImpactSpeed_A = ((2 * Ball_B.Velocity.Length()) + Ball_A.Velocity.Length()) / 4.0f;
            float ImpactSpeed_B = ((2 * Ball_A.Velocity.Length()) + Ball_B.Velocity.Length()) / 4.0f;

            //Impulse *= (float)Math.Sqrt(ImpactSpeed) * 0.75f;

            Ball_A.Velocity += Impulse * ImpactSpeed_A;
            Ball_B.Velocity -= Impulse * ImpactSpeed_B;
            //Ball_A.Velocity += Impulse / Ball_A.Mass;
            //Ball_B.Velocity -= Impulse / Ball_B.Mass;

            //Vector2 Velocity_A = Ball_A.Velocity;
            //Vector2 Velocity_B = Ball_B.Velocity;
            //Vector2 Direction_BA = Vector2.Normalize(Ball_A.Position - Ball_B.Position);

            //Ball_B.Velocity += Velocity_A * 0.75f;
            //Ball_A.Velocity += Direction_BA * Velocity_B.Length() * 0.75f;
        }
        void Bounce_Ball_Rim(Basketball Ball_A, Vector2 Rim_B)
        {
            Vector2 Direction_BA = Vector2.Normalize(Ball_A.Position - Rim_B);

            Ball_A.Velocity = Vector2.Reflect(Ball_A.Velocity, Direction_BA);
        }
        void Penetration_Ball(Basketball Ball_A, Basketball Ball_B)
        {
            float Penetration = (Ball_A.Radius + Ball_B.Radius) - Vector2.Distance(Ball_A.Position, Ball_B.Position);
            Vector2 Direction_AB = Vector2.Normalize(Ball_B.Position - Ball_A.Position);
            Vector2 Direction_BA = Vector2.Negate(Direction_AB);

            Ball_A.Position += Direction_BA * Penetration * 0.52f;
            Ball_B.Position += Direction_AB * Penetration * 0.52f;
        }
        void Penetration_Circle(Basketball Ball_A, Vector2 Rim_B)
        {
            float Penetration = (Ball_A.Radius + Goal.RIM_RADIUS) - Vector2.Distance(Ball_A.Position, Rim_B);
            Vector2 Direction_AB = Vector2.Normalize(Rim_B - Ball_A.Position);
            Vector2 Direction_BA = Vector2.Negate(Direction_AB);

            Ball_A.Position += Direction_BA * Penetration * 1.05f;
        }

        #endregion

        #region Properties

        public GamePage GamePage
        {
            get;
            protected set;
        }

        protected List<Basketball> BasketBalls
        {
            get;
            set;
        }
        protected List<Goal> Goals
        {
            get;
            set;
        }

        protected Rectangle Wall
        {
            get;
            set;
        }

        protected float BounceFriction
        {
            get;
            set;
        }
        protected float RollFriction
        {
            get;
            set;
        }
        protected float AirFriction
        {
            get;
            set;
        }

        protected float GravityStrength
        {
            get;
            set;
        }
        protected Vector2 GravityDirection
        {
            get;
            set;
        }


        #endregion

    }
}
